package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.*;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

import static com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig.*;
import static com.alibaba.fastjson.serializer.SerializerFeature.*;

@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
public class DynamicRuleConfig {
    final ConfigService configService;

    @Bean
    public DynamicRuleProvider<List<FlowRuleEntity>> flowNacosProvider() {
        return new NacosProvider<>(FLOW_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, FlowRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<FlowRuleEntity>> flowNacosPublisher() {
        return new NacosPublisher<>(FLOW_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<DegradeRuleEntity>> degradeNacosProvider() {
        return new NacosProvider<>(DEGRADE_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, DegradeRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<DegradeRuleEntity>> degradeNacosPublisher() {
        return new NacosPublisher<>(DEGRADE_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<ParamFlowRuleEntity>> paramFlowNacosProvider() {
        return new NacosProvider<>(PARAM_FLOW_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, ParamFlowRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<ParamFlowRuleEntity>> paramFlowNacosPublisher() {
        return new NacosPublisher<>(PARAM_FLOW_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<SystemRuleEntity>> systemNacosProvider() {
        return new NacosProvider<>(SYSTEM_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, SystemRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<SystemRuleEntity>> systemNacosPublisher() {
        return new NacosPublisher<>(SYSTEM_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<AuthorityRuleEntity>> authorityNacosProvider() {
        return new NacosProvider<>(AUTHORITY_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, AuthorityRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<AuthorityRuleEntity>> authorityNacosPublisher() {
        return new NacosPublisher<>(AUTHORITY_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<GatewayFlowRuleEntity>> gwFlowNacosProvider() {
        return new NacosProvider<>(GW_FLOW_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, GatewayFlowRuleEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<GatewayFlowRuleEntity>> gwFlowNacosPublisher() {
        return new NacosPublisher<>(GW_FLOW_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Bean
    public DynamicRuleProvider<List<ApiDefinitionEntity>> gwApiGroupNacosProvider() {
        return new NacosProvider<>(GW_API_GROUP_DATA_ID_POSTFIX, configService,
                s -> JSON.parseArray(s, ApiDefinitionEntity.class)
        );
    }

    @Bean
    public DynamicRulePublisher<List<ApiDefinitionEntity>> gwApiGroupNacosPublisher() {
        return new NacosPublisher<>(GW_API_GROUP_DATA_ID_POSTFIX, configService,
                o -> JSON.toJSONString(o, PrettyFormat, WriteMapNullValue, WriteDateUseDateFormat)
        );
    }

    @Slf4j
    @RequiredArgsConstructor
    static class NacosProvider<T> implements DynamicRuleProvider<List<T>> {
        final String dataIdPostfix;
        final ConfigService configService;
        final Converter<String, List<T>> converter;

        @Override
        public List<T> getRules(String appName) throws Exception {
            String rules = configService.getConfig(
                appName.concat(dataIdPostfix).concat(".").concat(ConfigType.JSON.getType()),
                NacosConfig.GROUP, 3000
            );
            log.info("obtain flow rules from nacos config:{}", rules);
            if (StringUtil.isEmpty(rules)) return new ArrayList<>();
            return converter.convert(rules);
        }
    }

    @Slf4j
    @RequiredArgsConstructor
    static class NacosPublisher<T> implements DynamicRulePublisher<List<T>> {
        final String dataIdPostfix;
        final ConfigService configService;
        final Converter<List<T>, String> converter;

        @Override
        public void publish(String appName, List<T> rules) throws Exception {
            AssertUtil.notEmpty(appName, "app name cannot be empty");
            if (rules == null) return;
            log.info("sentinel dashboard push rules: {}", rules);
            configService.publishConfig(
                appName.concat(dataIdPostfix).concat(".").concat(ConfigType.JSON.getType()),
                NacosConfig.GROUP, converter.convert(rules), ConfigType.JSON.getType()
            );
        }
    }
}